﻿using System;
using System.Collections.Generic;
using System.Linq;

using Perseus;

namespace Hetymine.Executioner.Data {
    public class ParameterInfo {
        public ParameterInfo() {
            this.Flags = new Dictionary<string, FlagInfo>();
            this.Parameters = new List<string>();
        }
        public ParameterInfo(string parameters) {
            this.Flags = new Dictionary<string, FlagInfo>();
            this.Parameters = new List<string>();

            SkipStringStream skip = new SkipStringStream();
            skip.AddSkipItem(new SkipItem("[", "]"));
            SkipItem skipItem = new SkipItem("\"", "\"", 1);
            skipItem.AddIgnoreString("\\\"");
            skip.AddSkipItem(skipItem);

            string[] prams = parameters.Split(" ", skip, SplitOptions.RemoveEmptyEntries);

            if (prams.Length > 0 && prams[0].StartsWith("-", StringComparison.Ordinal)) {
                int len = prams[0].Length - 1;
                for (int i = 1; i <= len; ++i) {
                    FlagInfo f = new FlagInfo(prams[0][i].ToString());

                    if (i != len && prams[0][i + 1] == '[') {
                        int pos = prams[0].IndexOf(']', i + 1);
                        // No closing bracket so ignore it so it gets treated as regular flag
                        if (pos < 0) {
                            continue;
                        }

                        string[] flagParameters = prams[0].Substring(i + 2, pos - i - 2).Split(
                            ",", 
                            skip, 
                            SplitOptions.RemoveEmptyEntries | SplitOptions.Trim
                        );

                        f.Parameters.AddRange(flagParameters);
                        i = pos;
                    }

                    this.Flags.Add(f.Flag, f);
                }

                PArray.RemoveItem(ref prams, 0);
            }

            // We don't include flags in the raw parameters
            this.RawParameters = prams.Join(" ");

            foreach (string p in prams) {                
                this.Parameters.Add(this.unescape(p));
            }
        }

        public Dictionary<string, FlagInfo> Flags { get; set; }
        public List<string> Parameters { get; set; }
        public string RawParameters { get; set; }
        public bool HasFlags(string flags) {
            for (int i = 0; i < flags.Length; ++i) {                
                if (!this.Flags.ContainsKey(flags[i].ToString())) {
                    return false;
                }
            }

            return true;
        }
        public bool IsEmpty { 
            get { return (this.Parameters.Count == 0); } 
        }
        public int Count {
            get { return this.Parameters.Count; }
        }

        public string InsertInto(string s) {
            return ParameterInfo.InsertInto(s, this.Parameters);
        }

        public void SetToRaw() {
            this.SetToRaw(1);
        }
        public void SetToRaw(int limit) {
            this.Parameters.Clear();
            if (this.RawParameters.IsNotEmpty()) {
                if (limit <= 1) {
                    this.Parameters.Add(this.RawParameters);
                }
                else {
                    // Flags are already parsed out
                    SkipStringStream skip = new SkipStringStream();
                    SkipItem skipItem = new SkipItem("\"", "\"", 1);
                    skipItem.AddIgnoreString("\\\"");
                    skip.AddSkipItem(skipItem);

                    string[] prams = this.RawParameters.Split(" ", limit, skip, SplitOptions.RemoveEmptyEntries);
                    int len = prams.Length;

                    // Only do raw if limit is reached
                    if (len == limit) {
                        --len;
                        for (int i = 0; i < len; ++i) {
                            this.Parameters.Add(this.unescape(prams[i]));
                        }

                        this.Parameters.Add(prams[len]);
                    }
                    else {
                        foreach (string p in prams) {
                            this.Parameters.Add(this.unescape(p));
                        }
                    }
                }
            }
        }

        private string unescape(string param) {
            string p = param;

            if (p.EnclosedWith("\"", "\"", StringComparison.Ordinal)) {
                p = p.Substring(1, p.Length - 2);
            }

            // Remove any escaped quotes
            p = p.Replace("\\\"", "\"").Replace("\\\\", "\\");

            return p;
        }

        public static string InsertInto(string s, IList<string> parameters) {
            // If sprint style inserts detected use sprint method
            if (s.IndexOf("%s") >= 0 || s.IndexOf("%d") >= 0) {
                return ParameterInfo.InsertIntoSprint(s, parameters);
            }

            return ParameterInfo.InsertIntoIndexed(s, parameters);
        }
        public static string InsertIntoSprint(string s, IList<string> parameters) {
            if (parameters == null || parameters.Count == 0) {
                return ParameterInfo.RemoveSprintInsertArguments(s);
            }

            SkipStringStream skip = new SkipStringStream();
            skip.AddIgnoreString("%%");

            string[] parts = s.Split(new string[]{"%s", "%d"},skip, SplitOptions.AddDelimiterToArray);

            string inserted = string.Empty;

            int i = 0;
            int j = 0; // Iteration count
            for (i = 0; i < parts.Length; ++i) {
                if (j >= parameters.Count) {
                    switch (parts[i]) {
                        case "%s":
                        case "%d":
                            break;
                        default:
                            inserted += parts[i];
                            break;
                    }

                    continue;
                }

                switch (parts[i]) {
                    case "%s":
                        inserted += parameters[j];
                        ++j;
                        break;
                    case "%d":
                        int number;
                        int.TryParse(parameters[j], out number);
                        inserted += number.ToString();
                        ++j;
                        break;
                    default:
                        inserted += parts[i];
                        break;
                }
            }

            inserted = inserted.Replace("%%", "%");

            return inserted;
        }
        public static string InsertIntoIndexed(string s, IList<string> parameters) {
            // Otherwise use {0} form, where 0 is the parameter index
            SkipStringStream skip = new SkipStringStream();
            skip.AddIgnoreString("{{");
            skip.AddIgnoreString("}}");

            string[] parts = s.Split(new string[] { "{", "}" }, skip, SplitOptions.AddDelimiterToArray);

            string inserted = string.Empty;

            int i = 0;
            int indexParam;
            bool inParam = false;
            for (i = 0; i < parts.Length; ++i) {
                switch (parts[i]) {
                    case "{":
                        inParam = true;
                        break;
                    default:
                        if (inParam) {
                            // Ensure its an actual parameter with a numeric index that is greater than or equal to 0
                            if (i + 1 < parts.Length && parts[i + 1] == "}" && int.TryParse(parts[i], out indexParam) && indexParam >= 0) {
                                if (parameters != null && indexParam < parameters.Count) {
                                    inserted += parameters[indexParam];
                                }

                                // Skip ahead over }
                                ++i;

                                inParam = false;
                                continue;
                            }

                            inParam = false;
                            inserted += "{";
                        }

                        inserted += parts[i];
                        break;
                }

            }

            return inserted;
        }
        
        public static string RemoveInsertArguments(string s) {
            if (s.IndexOf("%s") >= 0 || s.IndexOf("%d") >= 0) {
                s = ParameterInfo.RemoveSprintInsertArguments(s);
            }
            else {
                s = ParameterInfo.RemoveIndexedInsertArguments(s);
            }

            return s;
        }
        public static string RemoveSprintInsertArguments(string s) {
            s = s.Replace("%s", string.Empty);
            s = s.Replace("%d", string.Empty);
            s = s.Replace("%%", "%");

            return s;
        }
        public static string RemoveIndexedInsertArguments(string s) {
            s = ParameterInfo.InsertIntoIndexed(s, null);

            return s;
        }

        public string Join() {
            return this.Join(0, -1, false);
        }
        public string Join(int start) {
            return this.Join(start, -1, false);
        }
        public string Join(bool update) {
            return this.Join(0, -1, update);
        }
        public string Join(int start, int length) {
            return this.Join(start, length, false);
        }
        /// <summary>
        /// Joins parameters from a specified start index to a specified length of parameter items.
        /// </summary>
        /// <param name="start">Parameter to start joining from.</param>
        /// <param name="length">Length of parameters to join.</param>
        /// <param name="update">When true, the internal parameter value will be updated to use the joined value.</param>
        /// <returns>The joined parameters.</returns>
        public string Join(int start, int length, bool update) {
            if (start < 0) {
                throw new IndexOutOfRangeException("Start index must be greater than or equal to zero");
            }

            int end;
            if (length == -1) {
                end = this.Parameters.Count - start;
            }
            else {
                end = Math.Min(start + length, this.Parameters.Count);
            }

            string result = string.Empty;
            if (update) {
                List<string> prams = new List<string>(); // New parameters list
                for (int i = 0; i < this.Parameters.Count; ++i) {
                    if (i == start) {
                        prams.Add(this.Parameters[i]);
                        result += this.Parameters[i];
                    }
                    else if (i > start && i <= end) {
                        prams[prams.Count - 1] += " " + this.Parameters[i];
                        result += " " + this.Parameters[i];
                    }
                    else {
                        prams.Add(this.Parameters[i]);
                    }
                }
                this.Parameters = prams;
            }
            else {
                if (this.Parameters.Count > start) {                    
                    for (int i = start; i <= end; ++i) {
                        if (result != string.Empty) {
                            result += " ";
                        }

                        result += this.Parameters[i];
                    }
                }
            }

            return result;
        }

        public override string ToString() {
            string prams = string.Empty;

            if (Flags.Count > 0) {
                prams += "-";
                foreach (FlagInfo f in this.Flags.Values) {
                    prams += f.ToString();
                }
            }

            if (this.Count > 0) {
                foreach (string p in this.Parameters) {
                    string pram = p;
                    if (pram.IndexOf(" ") >= 0) {
                        pram = "\"" + pram.Replace("\\", "\\\\").Replace("\"", "\\\"") + "\"";
                    }

                    prams += " " + pram;
                }

            }            

            return prams;
        }
    }
}
